package com.ehsunbehravesh.mypasswords.exportimport;

import com.ehsunbehravesh.mypasswords.Domain;
import com.ehsunbehravesh.mypasswords.Logger;
import com.ehsunbehravesh.mypasswords.Utils;
import com.ehsunbehravesh.mypasswords.entity.PasswordEntry;
import java.io.File;
import java.util.Observable;
import javax.swing.JOptionPane;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class XMLImporter extends Observable implements Runnable {

  private int passwordsCount;
  private int tagsCount;
  private File file;
  private String key;
  private int minProgress;
  private int maxProgress;
  private int progress;
  private boolean overWrite;
  private boolean allFieldsAreEncrypted;

  //<editor-fold defaultstate="collapsed" desc="Constructors">
  public XMLImporter(File file, String key, boolean overWrite, boolean allFieldsAreEncrypted) {
    this.file = file;
    this.key = key;
    this.minProgress = 0;
    this.maxProgress = 0;
    this.progress = 0;
    this.passwordsCount = 0;
    this.tagsCount = 0;
    this.overWrite = overWrite;
    this.allFieldsAreEncrypted = allFieldsAreEncrypted;
  }
  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Methods">
  @Override
  public void run() {
    Domain domain = Domain.getInstance();
    try {
      DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
      Document document = documentBuilder.parse(file);

      Element elmRoot = document.getDocumentElement();
      elmRoot.normalize();

      //<editor-fold desc="Specifications Tags Passwords">
      NodeList nodesSpecification = elmRoot.getElementsByTagName("Specifications");
      if (nodesSpecification.getLength() < 1) {
        showErrorDialog();
        throw new ImportException("There is no Specifications element.");
      } else if (nodesSpecification.getLength() > 1) {
        showErrorDialog();
        throw new ImportException("There is more than one Specifications element.");
      }
      Element elmSpecifications = (Element) nodesSpecification.item(0);

      NodeList nodesPasswords = elmRoot.getElementsByTagName("Passwords");
      if (nodesPasswords.getLength() < 1) {
        showErrorDialog();
        throw new ImportException("There is no Passwords element.");
      } else if (nodesPasswords.getLength() > 1) {
        showErrorDialog();
        throw new ImportException("There is more than one Passwords element.");
      }
      Element elmPasswords = (Element) nodesPasswords.item(0);

      NodeList nodesVersion = elmSpecifications.getElementsByTagName("MyPasswordsVersion");
      if (nodesVersion.getLength() < 1) {
        showErrorDialog();
        throw new ImportException("There is no MyPasswordsVersion element.");
      }
      Element elmVersion = (Element) nodesVersion.item(0);
      String strVersion = elmVersion.getTextContent().trim();
      float version = Float.parseFloat(strVersion);
      if (version > domain.getMyPasswordsVersion()) {
        showErrorDialog();
        throw new ImportException("The XML file has been created by a newer version of MyPasswords.");
      }
      //</editor-fold>

      //<editor-fold desc="Update PasswordsCount & TagsCount">
      NodeList nodesPasswordsCount = elmSpecifications.getElementsByTagName("PasswordsCount");
      if (nodesPasswordsCount.getLength() < 1) {
        showErrorDialog();
        throw new ImportException("There is no PasswordsCount element.");
      }
      Element elmPasswordsCount = (Element) nodesPasswordsCount.item(0);
      this.passwordsCount = Integer.parseInt(elmPasswordsCount.getTextContent().trim());

      NodeList nodesTagsCount = elmSpecifications.getElementsByTagName("TagsCount");
      if (nodesTagsCount.getLength() < 1) {
        showErrorDialog();
        throw new ImportException("There is no TagsCount element.");
      }
      Element elmTagsCount = (Element) nodesTagsCount.item(0);
      this.tagsCount = Integer.parseInt(elmTagsCount.getTextContent().trim());

      this.maxProgress = this.passwordsCount;
      setChanged();
      notifyObservers("init");
      //</editor-fold>      

      //<editor-fold desc="Import Passwords">
      nodesPasswords = elmPasswords.getElementsByTagName("Password");
      for (int i = 0; i < nodesPasswords.getLength(); i++) {
        Element elmPassword = (Element) nodesPasswords.item(i);
        if (elmPassword.getChildNodes().getLength() < 2) {
          continue;
        }

        NodeList nodesTemp = elmPassword.getElementsByTagName("Title");
        if (nodesTemp.getLength() < 1) {
          showErrorDialog();
          throw new ImportException("There is no Title element.");
        }
        Element elmTitle = (Element) nodesTemp.item(0);

        nodesTemp = elmPassword.getElementsByTagName("Description");
        if (nodesTemp.getLength() < 1) {
          showErrorDialog();
          throw new ImportException("There is no Description element.");
        }
        Element elmDescription = (Element) nodesTemp.item(0);

        nodesTemp = elmPassword.getElementsByTagName("Username");
        if (nodesTemp.getLength() < 1) {
          showErrorDialog();
          throw new ImportException("There is no Username element.");
        }
        Element elmUsername = (Element) nodesTemp.item(0);

        nodesTemp = elmPassword.getElementsByTagName("Password");
        if (nodesTemp.getLength() < 1) {
          showErrorDialog();
          throw new ImportException("There is no Password element.");
        }
        Element elmPasswordValue = (Element) nodesTemp.item(0);

        nodesTemp = elmPassword.getElementsByTagName("URL");
        if (nodesTemp.getLength() < 1) {
          showErrorDialog();
          throw new ImportException("There is no URL element.");
        }
        Element elmURL = (Element) nodesTemp.item(0);

        PasswordEntry passwordEntry = new PasswordEntry(elmTitle.getTextContent().trim());
        if (allFieldsAreEncrypted) {
          passwordEntry.setTitle(decrypt(passwordEntry.getTitle()));
        }

        if (passwordEntry.loadByTitle(true) && passwordEntry.getId() > 0) {
          if (this.overWrite) {
            if (allFieldsAreEncrypted) {
              passwordEntry.setUrl(decrypt(elmURL.getTextContent().trim()));
              passwordEntry.setDescription(decrypt(elmDescription.getTextContent().trim()));
              passwordEntry.setUsername(decrypt(elmUsername.getTextContent().trim()));
            } else {
              passwordEntry.setUrl(elmURL.getTextContent().trim());
              passwordEntry.setDescription(elmDescription.getTextContent().trim());
              passwordEntry.setUsername(elmUsername.getTextContent().trim());
            }
            passwordEntry.setPassword(decrypt(elmPasswordValue.getTextContent()));

            passwordEntry.save();
          }
        } else {
          if (allFieldsAreEncrypted) {
            passwordEntry.setUrl(decrypt(elmURL.getTextContent().trim()));
            passwordEntry.setDescription(decrypt(elmDescription.getTextContent().trim()));
            passwordEntry.setUsername(decrypt(elmUsername.getTextContent().trim()));
          } else {
            passwordEntry.setUrl(elmURL.getTextContent().trim());
            passwordEntry.setDescription(elmDescription.getTextContent().trim());
            passwordEntry.setUsername(elmUsername.getTextContent().trim());
          }
          passwordEntry.setPassword(decrypt(elmPasswordValue.getTextContent()));

          passwordEntry.save();
        }

        NodeList nodesTags = elmPassword.getElementsByTagName("Tag");
        if (this.overWrite) {
          passwordEntry.clearTags();
        }
        for (int j = 0; j < nodesTags.getLength(); j++) {
          Element elmTag = (Element) nodesTags.item(j);
          if (allFieldsAreEncrypted) {
            passwordEntry.addTag(decrypt(elmTag.getTextContent().trim()));
          } else {
            passwordEntry.addTag(elmTag.getTextContent().trim());
          }
        }
        this.progress++;
        setChanged();
        notifyObservers("progress");
      }
      //</editor-fold>

    } catch (Exception ex) {
      Logger.log(ex.getMessage());
    }
  }

  private String decrypt(String text) {
    try {
      return Utils.decrypt(this.key, text);
    } catch (Exception ex) {
      Logger.log(ex.getMessage());
      return "";
    }
  }

  private void showErrorDialog() {
    JOptionPane.showMessageDialog(null, "Error in import process!\nCheck the log file for more details.", "Error", JOptionPane.ERROR_MESSAGE);
  }
  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Getters & Setters">
  public File getFile() {
    return file;
  }

  public String getKey() {
    return key;
  }

  public int getMaxProgress() {
    return maxProgress;
  }

  public int getMinProgress() {
    return minProgress;
  }

  public int getProgress() {
    return progress;
  }

  public int getProgressPercent() {
    return (int) Math.floor(progress * 100 / maxProgress);
  }

  public int getPasswordsCount() {
    return passwordsCount;
  }

  public int getTagsCount() {
    return tagsCount;
  }

  public boolean isOverWrite() {
    return overWrite;
  }

  public void setOverWrite(boolean overWrite) {
    this.overWrite = overWrite;
  }
  //</editor-fold>
}

